package org.darcy.config;

import java.security.NoSuchAlgorithmException;
import java.util.Map;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.darcy.config.property.ShiroProperties;
import org.darcy.config.shiro.ShiroService;
import org.darcy.config.shiro.credentials.RetryLimitCredentialsMatcher;
import org.darcy.config.shiro.realm.ShiroRealm;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

/**
 * Shiro配置类
 */
@Configuration
public class ShiroConfig {

	/**
	 * session有效期，一小时
	 */
	private static final int expire = 3600;

	@Autowired
	private ShiroService shiroService;

	@Autowired
	private ShiroProperties shiroProperties;

	@Bean(name = "lifecycleBeanPostProcessor")
	public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * 修复UnavailableSecurityManagerException（详见issues#IK7C3）
	 *
	 * @param securityManager
	 * @return
	 */
	@Bean
	public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager) {
		MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
		bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
		bean.setArguments(securityManager);
		return bean;
	}

	/**
	 * ShiroFilterFactoryBean 处理拦截资源文件问题。 注意：单独一个ShiroFilterFactoryBean配置是或报错的，因为在
	 * 初始化ShiroFilterFactoryBean的时候需要注入：SecurityManager Filter Chain定义说明
	 * 1、一个URL可以配置多个Filter，使用逗号分隔 2、当设置多个过滤器时，全部验证通过，才视为通过 3、部分过滤器可指定参数，如perms，roles
	 */
	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		shiroFilterFactoryBean.setLoginUrl(shiroProperties.getLoginUrl());
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl(shiroProperties.getSuccessUrl());
		// 未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl());
		// 配置数据库中的resource
		Map<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	@Bean
	@DependsOn("lifecycleBeanPostProcessor")
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
		creator.setProxyTargetClass(true);
		return creator;
	}

	@Bean(name = "securityManager")
	public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm authRealm) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 设置realm.
		securityManager.setRealm(authRealm);
		// 自定义session管理 使用redis
		securityManager.setSessionManager(sessionManager());
		// 注入记住我管理器
		securityManager.setRememberMeManager(rememberMeManager());
		return securityManager;
	}

	@Bean(name = "shiroRealm")
	public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") RetryLimitCredentialsMatcher matcher) {
		ShiroRealm shiroRealm = new ShiroRealm();
		shiroRealm.setCredentialsMatcher(credentialsMatcher());
		return shiroRealm;
	}

	/**
	 * 凭证匹配器 （由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
	 * 所以我们需要修改下doGetAuthenticationInfo中的代码; ）
	 *
	 * @return
	 */
	@Bean(name = "credentialsMatcher")
	public RetryLimitCredentialsMatcher credentialsMatcher() {
		return new RetryLimitCredentialsMatcher();
	}

	/**
	 * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
	 *
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	/**
	 * shiro session的管理
	 */
	@Bean
	public DefaultWebSessionManager sessionManager() {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		sessionManager.setGlobalSessionTimeout(expire * 1000L);
		return sessionManager;
	}

	/**
	 * cookie对象;
	 *
	 * @return
	 */
	public SimpleCookie rememberMeCookie() {
		// 这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
		SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
		// 记住我cookie生效时间30天 ,单位秒。
		simpleCookie.setMaxAge(expire * 24 * 30);
		return simpleCookie;
	}

	/**
	 * cookie管理对象;记住我功能
	 *
	 * @return
	 */
	public CookieRememberMeManager rememberMeManager() {
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		// 使用自定义的序列化类
		cookieRememberMeManager.setCookie(rememberMeCookie());
		// rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 192 256 位)
		cookieRememberMeManager.setCipherKey(GenerateCipherKey.generateNewKey());
		return cookieRememberMeManager;
	}

	/**
	 * 解决 shiro 反序列化漏洞
	 *
	 * https://blog.csdn.net/qq_34775355/article/details/106643678
	 */
	public static class GenerateCipherKey {

		/**
		 * 随机生成秘钥，参考org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey(int)
		 *
		 * @return byte[]
		 */
		public static byte[] generateNewKey() {
			KeyGenerator kg;
			try {
				kg = KeyGenerator.getInstance("AES");
			} catch (NoSuchAlgorithmException var5) {
				String msg = "Unable to acquire AES algorithm.  This is required to function.";
				throw new IllegalStateException(msg, var5);
			}
			// 满足合规应使用256位
			kg.init(256);
			SecretKey key = kg.generateKey();
			return key.getEncoded();
		}
	}
}
